<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - adaptive tone-mapping</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
		<style>
			#labels {
				position: absolute;
				display: grid;
				grid-template-columns: 1fr 1fr 1fr;
				justify-items: center;
				align-items: end;
				height: 100%;
				width: 100%;
			}
			#labels > div {
				background-color: black;
				padding: 6px;
				margin-bottom: 20%;
			}
		</style>
	</head>

	<body>
		<div id="d">
			<div id="info">
				<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl demo :
				Earth diffuse and city lights by <a href="http://seanward.org" target="_blank" rel="noopener">Sean Ward</a>
			</div>
			<div id="labels">
				<div>
					Low Dynamic Range<br/>
					Static Tone Mapping
				</div>
				<div>
					High Dynamic Range<br/>
					Static Tone Mapping
				</div>
				<div>
					High Dynamic Range<br/>
					Adaptive Tone Mapping
				</div>
			</div>
		</div>

		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js",
					"three/addons/": "./jsm/"
				}
			}
		</script>

		<script type="module">

			import * as THREE from 'three';

			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
			import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
			import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
			import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
			import { AdaptiveToneMappingPass } from 'three/addons/postprocessing/AdaptiveToneMappingPass.js';
			import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
			import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';

			let bloomPass, adaptToneMappingPass, ldrToneMappingPass, hdrToneMappingPass;
			let params;

			let camera, scene, renderer, dynamicHdrEffectComposer, hdrEffectComposer, ldrEffectComposer;
			let cameraCube, sceneCube;
			let cameraBG, debugScene;
			let adaptiveLuminanceMat, currentLuminanceMat;

			let directionalLight;

			let orbitControls;

			let windowHalfX = window.innerWidth / 2;
			let windowHalfY = window.innerHeight / 2;

			let windowThirdX = window.innerWidth / 3;

			init();
			animate();

			function init() {

				params = {
					bloomAmount: 1.0,
					sunLight: 4.0,

					enabled: true,
					avgLuminance: 0.7,
					middleGrey: 0.04,
					maxLuminance: 16,

					adaptionRate: 2.0
				};

				const container = document.createElement( 'div' );
				document.body.appendChild( container );

				// CAMERAS

				camera = new THREE.PerspectiveCamera( 70, windowThirdX / window.innerHeight, 0.1, 100000 );
				camera.position.x = 700;
				camera.position.y = 400;
				camera.position.z = 800;
				cameraCube = new THREE.PerspectiveCamera( 70, windowThirdX / window.innerHeight, 1, 100000 );

				cameraBG = new THREE.OrthographicCamera( - windowHalfX, windowHalfX, windowHalfY, - windowHalfY, - 10000, 10000 );
				cameraBG.position.z = 100;

				orbitControls = new OrbitControls( camera, container );
				orbitControls.autoRotate = true;
				orbitControls.autoRotateSpeed = 1;

				// SCENE

				scene = new THREE.Scene();
				sceneCube = new THREE.Scene();
				debugScene = new THREE.Scene();

				// LIGHTS

				const ambient = new THREE.AmbientLight( 0x050505 );
				scene.add( ambient );

				directionalLight = new THREE.DirectionalLight( 0xffffff, params.sunLight );
				directionalLight.position.set( 2, 0, 10 ).normalize();
				scene.add( directionalLight );

				const atmoShader = {
					side: THREE.BackSide,
					// blending: THREE.AdditiveBlending,
					transparent: true,
					lights: true,
					uniforms: THREE.UniformsUtils.merge( [

						THREE.UniformsLib[ 'common' ],
						THREE.UniformsLib[ 'lights' ]

					] ),
					vertexShader: [
						'varying vec3 vViewPosition;',
						'varying vec3 vNormal;',
						'void main() {',
						THREE.ShaderChunk[ 'beginnormal_vertex' ],
						THREE.ShaderChunk[ 'defaultnormal_vertex' ],

						'	vNormal = normalize( transformedNormal );',
						'vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
						'vViewPosition = -mvPosition.xyz;',
						'gl_Position = projectionMatrix * mvPosition;',
						'}'

					].join( '\n' ),

					fragmentShader: [

						THREE.ShaderChunk[ 'common' ],
						THREE.ShaderChunk[ 'bsdfs' ],
						THREE.ShaderChunk[ 'lights_pars_begin' ],
						THREE.ShaderChunk[ 'normal_pars_fragment' ],
						THREE.ShaderChunk[ 'lights_phong_pars_fragment' ],

						'void main() {',
						'vec3 normal = normalize( -vNormal );',
						'vec3 viewPosition = normalize( vViewPosition );',
						'#if NUM_DIR_LIGHTS > 0',

						'vec3 dirDiffuse = vec3( 0.0 );',

						'for( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {',

						'vec4 lDirection = viewMatrix * vec4( directionalLights[i].direction, 0.0 );',
						'vec3 dirVector = normalize( lDirection.xyz );',
						'float dotProduct = dot( viewPosition, dirVector );',
						'dotProduct = 1.0 * max( dotProduct, 0.0 ) + (1.0 - max( -dot( normal, dirVector ), 0.0 ));',
						'dotProduct *= dotProduct;',
						'dirDiffuse += max( 0.5 * dotProduct, 0.0 ) * directionalLights[i].color;',
						'}',
						'#endif',

						//Fade out atmosphere at edge
						'float viewDot = abs(dot( normal, viewPosition ));',
						'viewDot = clamp( pow( viewDot + 0.6, 10.0 ), 0.0, 1.0);',

						'vec3 color = vec3( 0.05, 0.09, 0.13 ) * dirDiffuse;',
						'gl_FragColor = vec4( color, viewDot );',

						'}'

					].join( '\n' )
				};

				const earthAtmoMat = new THREE.ShaderMaterial( atmoShader );

				const earthMat = new THREE.MeshPhongMaterial( {
					color: 0xffffff,
					shininess: 200
				} );

				const textureLoader = new THREE.TextureLoader();

				textureLoader.load( 'textures/planets/earth_atmos_4096.jpg', function ( tex ) {

					earthMat.map = tex;
					earthMat.map.encoding = THREE.sRGBEncoding;
					earthMat.needsUpdate = true;

				} );
				textureLoader.load( 'textures/planets/earth_specular_2048.jpg', function ( tex ) {

					earthMat.specularMap = tex;
					earthMat.specularMap.encoding = THREE.sRGBEncoding;
					earthMat.needsUpdate = true;

				} );

				// let earthNormal = textureLoader.load( 'textures/planets/earth-new-normal-2048.jpg', function( tex ) {
				// 	earthMat.normalMap = tex;
				// 	earthMat.needsUpdate = true;
				// } );

				const earthLights = textureLoader.load( 'textures/planets/earth_lights_2048.png' );
				earthLights.encoding = THREE.sRGBEncoding;

				const earthLightsMat = new THREE.MeshBasicMaterial( {
					color: 0xffffff,
					blending: THREE.AdditiveBlending,
					transparent: true,
					depthTest: false,
					map: earthLights,

				} );

				const clouds = textureLoader.load( 'textures/planets/earth_clouds_2048.png' );
				clouds.encoding = THREE.sRGBEncoding;

				const earthCloudsMat = new THREE.MeshLambertMaterial( {
					color: 0xffffff,
					blending: THREE.NormalBlending,
					transparent: true,
					depthTest: false,
					map: clouds
				} );


				const earthGeo = new THREE.SphereGeometry( 600, 24, 24 );
				const sphereMesh = new THREE.Mesh( earthGeo, earthMat );
				scene.add( sphereMesh );

				const sphereLightsMesh = new THREE.Mesh( earthGeo, earthLightsMat );
				scene.add( sphereLightsMesh );

				const sphereCloudsMesh = new THREE.Mesh( earthGeo, earthCloudsMat );
				scene.add( sphereCloudsMesh );

				const sphereAtmoMesh = new THREE.Mesh( earthGeo, earthAtmoMat );
				sphereAtmoMesh.scale.set( 1.05, 1.05, 1.05 );
				scene.add( sphereAtmoMesh );

				const vBGShader = [
					// "attribute vec2 uv;",
					'varying vec2 vUv;',
					'void main() {',
					'vUv = uv;',
					'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
					'}'

				].join( '\n' );

				const pBGShader = [

					'uniform sampler2D map;',
					'varying vec2 vUv;',

					'void main() {',

					'vec2 sampleUV = vUv;',
					'vec4 color = texture2D( map, sampleUV, 0.0 );',

					'gl_FragColor = vec4( color.xyz, 1.0 );',

					'}'

				].join( '\n' );

				// Skybox
				adaptiveLuminanceMat = new THREE.ShaderMaterial( {
					uniforms: {
						'map': { value: null }
					},
					vertexShader: vBGShader,
					fragmentShader: pBGShader,
					depthTest: false,
					// color: 0xffffff
					blending: THREE.NoBlending
				} );

				currentLuminanceMat = new THREE.ShaderMaterial( {
					uniforms: {
						'map': { value: null }
					},
					vertexShader: vBGShader,
					fragmentShader: pBGShader,
					depthTest: false
					// color: 0xffffff
					// blending: THREE.NoBlending
				} );

				let quadBG = new THREE.Mesh( new THREE.PlaneGeometry( 0.1, 0.1 ), currentLuminanceMat );
				quadBG.position.z = - 500;
				quadBG.position.x = - window.innerWidth * 0.5 + window.innerWidth * 0.05;
				quadBG.scale.set( window.innerWidth, window.innerHeight, 1 );
				debugScene.add( quadBG );

				quadBG = new THREE.Mesh( new THREE.PlaneGeometry( 0.1, 0.1 ), adaptiveLuminanceMat );
				quadBG.position.z = - 500;
				quadBG.position.x = - window.innerWidth * 0.5 + window.innerWidth * 0.15;
				quadBG.scale.set( window.innerWidth, window.innerHeight, 1 );
				debugScene.add( quadBG );

				const r = 'textures/cube/MilkyWay/';
				const urls = [ r + 'dark-s_px.jpg', r + 'dark-s_nx.jpg',
							 r + 'dark-s_py.jpg', r + 'dark-s_ny.jpg',
							 r + 'dark-s_pz.jpg', r + 'dark-s_nz.jpg' ];

				const textureCube = new THREE.CubeTextureLoader().load( urls );
				textureCube.encoding = THREE.sRGBEncoding;

				sceneCube.background = textureCube;

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.autoClear = false;

				container.appendChild( renderer.domElement );

				// let width = window.innerWidth || 1;
				const height = window.innerHeight || 1;

				const parameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat };
				const regularRenderTarget = new THREE.WebGLRenderTarget( windowThirdX, height, parameters );
				ldrEffectComposer = new EffectComposer( renderer, regularRenderTarget );

				parameters.type = THREE.FloatType;

				if ( renderer.capabilities.isWebGL2 === false && renderer.extensions.has( 'OES_texture_half_float_linear' ) === false ) {

					parameters.type = undefined; // avoid usage of floating point textures

				}

				const hdrRenderTarget = new THREE.WebGLRenderTarget( windowThirdX, height, parameters );
				dynamicHdrEffectComposer = new EffectComposer( renderer, hdrRenderTarget );
				dynamicHdrEffectComposer.setSize( window.innerWidth, window.innerHeight );
				hdrEffectComposer = new EffectComposer( renderer, hdrRenderTarget );

				const debugPass = new RenderPass( debugScene, cameraBG );
				debugPass.clear = false;
				const scenePass = new RenderPass( scene, camera, undefined, undefined, undefined );
				const skyboxPass = new RenderPass( sceneCube, cameraCube );
				scenePass.clear = false;

				adaptToneMappingPass = new AdaptiveToneMappingPass( true, 256 );
				adaptToneMappingPass.needsSwap = true;
				ldrToneMappingPass = new AdaptiveToneMappingPass( false, 256 );
				hdrToneMappingPass = new AdaptiveToneMappingPass( false, 256 );
				bloomPass = new BloomPass();
				const gammaCorrectionPass = new ShaderPass( GammaCorrectionShader );

				dynamicHdrEffectComposer.addPass( skyboxPass );
				dynamicHdrEffectComposer.addPass( scenePass );
				dynamicHdrEffectComposer.addPass( adaptToneMappingPass );
				dynamicHdrEffectComposer.addPass( bloomPass );
				dynamicHdrEffectComposer.addPass( gammaCorrectionPass );

				hdrEffectComposer.addPass( skyboxPass );
				hdrEffectComposer.addPass( scenePass );
				hdrEffectComposer.addPass( hdrToneMappingPass );
				hdrEffectComposer.addPass( bloomPass );
				hdrEffectComposer.addPass( gammaCorrectionPass );

				ldrEffectComposer.addPass( skyboxPass );
				ldrEffectComposer.addPass( scenePass );
				ldrEffectComposer.addPass( ldrToneMappingPass );
				ldrEffectComposer.addPass( bloomPass );
				ldrEffectComposer.addPass( gammaCorrectionPass );

				const gui = new GUI();

				const sceneGui = gui.addFolder( 'Scenes' );
				const toneMappingGui = gui.addFolder( 'ToneMapping' );
				const staticToneMappingGui = gui.addFolder( 'StaticOnly' );
				const adaptiveToneMappingGui = gui.addFolder( 'AdaptiveOnly' );

				sceneGui.add( params, 'bloomAmount', 0.0, 10.0 );
				sceneGui.add( params, 'sunLight', 0.1, 12.0 );

				toneMappingGui.add( params, 'enabled' );
				toneMappingGui.add( params, 'middleGrey', 0, 12 );
				toneMappingGui.add( params, 'maxLuminance', 1, 30 );

				staticToneMappingGui.add( params, 'avgLuminance', 0.001, 2.0 );
				adaptiveToneMappingGui.add( params, 'adaptionRate', 0.0, 10.0 );

				gui.open();

				window.addEventListener( 'resize', onWindowResize );

			}

			function onWindowResize() {

				windowHalfX = window.innerWidth / 2;
				windowHalfY = window.innerHeight / 2;
				windowThirdX = window.innerWidth / 3;

				camera.aspect = windowThirdX / window.innerHeight;
				camera.updateProjectionMatrix();

				cameraCube.aspect = windowThirdX / window.innerHeight;
				cameraCube.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			function animate() {

				requestAnimationFrame( animate );
				if ( bloomPass ) {

					bloomPass.combineUniforms[ 'strength' ].value = params.bloomAmount;

				}

				if ( adaptToneMappingPass ) {

					adaptToneMappingPass.setAdaptionRate( params.adaptionRate );
					adaptiveLuminanceMat.uniforms[ 'map' ].value = adaptToneMappingPass.luminanceRT;
					currentLuminanceMat.uniforms[ 'map' ].value = adaptToneMappingPass.currentLuminanceRT;

					adaptToneMappingPass.enabled = params.enabled;
					adaptToneMappingPass.setMaxLuminance( params.maxLuminance );
					adaptToneMappingPass.setMiddleGrey( params.middleGrey );

					hdrToneMappingPass.enabled = params.enabled;
					hdrToneMappingPass.setMaxLuminance( params.maxLuminance );
					hdrToneMappingPass.setMiddleGrey( params.middleGrey );
					if ( hdrToneMappingPass.setAverageLuminance ) {

						hdrToneMappingPass.setAverageLuminance( params.avgLuminance );

					}

					ldrToneMappingPass.enabled = params.enabled;
					ldrToneMappingPass.setMaxLuminance( params.maxLuminance );
					ldrToneMappingPass.setMiddleGrey( params.middleGrey );
					if ( ldrToneMappingPass.setAverageLuminance ) {

						ldrToneMappingPass.setAverageLuminance( params.avgLuminance );

					}

				}

				directionalLight.intensity = params.sunLight;

				orbitControls.update();

				render();

			}

			function render() {

				camera.lookAt( scene.position );
				cameraCube.rotation.copy( camera.rotation );

				renderer.setViewport( 0, 0, windowThirdX, window.innerHeight );
				ldrEffectComposer.render( 0.017 );

				renderer.setViewport( windowThirdX, 0, windowThirdX, window.innerHeight );
				hdrEffectComposer.render( 0.017 );

				renderer.setViewport( windowThirdX * 2, 0, windowThirdX, window.innerHeight );
				dynamicHdrEffectComposer.render( 0.017 );

			}

		</script>

	</body>
</html>
